---
image: /generated/articles-docs-video-uploads.png
id: video-uploads
title: Handling user video uploads
crumb: 'Building video apps'
---

In an app where users can upload videos and edit them, we can create a better user experience by loading the video into a player even before the upload is finished. Good news: This can be done pretty easily!

## Allowing user uploads

We have a component which returns a [`<OffthreadVideo>`](/docs/offthreadvideo) tag that includes an URL as source.

```tsx twoslash title="MyComposition.tsx"
const upload = async (file: File) => {
  return 'https://example.com';
};
// ---cut---
import {AbsoluteFill, OffthreadVideo} from 'remotion';

type VideoProps = {
  videoURL: string;
};

export const MyComponent: React.FC<VideoProps> = ({videoURL}) => {
  return (
    <AbsoluteFill>
      <OffthreadVideo src={videoURL} />
    </AbsoluteFill>
  );
};
```

The video URL will be passed from the Remotion Player to our component.  
Using a `<input type="file">` element, we allow a user upload.  
As soon as a file is fully uploaded to the cloud, the URL will be set and can be used by the component to display the video.

```tsx twoslash title="App.tsx"
import {useCallback} from 'react';
const MyComposition: React.FC<{videoUrl: string | null}> = (URL) => {
  return null;
};
const upload = async (file: File) => {
  return 'https://exampleName.s3.examplesRegion.amazonaws.com';
};

// ---cut---
import {Player} from '@remotion/player';
import {useState} from 'react';

export const RemotionPlayer: React.FC = () => {
  const [videoUrl, setVideoUrl] = useState<string | null>(null);

  const handleChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files === null) {
      return;
    }

    const file = event.target.files[0];
    //upload is an example function  & returns a URL when a file is uploaded on the cloud.
    const cloudURL = await upload(file);
    // E.g., cloudURL = https://exampleBucketName.s3.ExampleAwsRegion.amazonaws.com
    setVideoUrl(cloudURL);
  }, []);

  return (
    <div>
      {videoUrl === null ? null : <Player component={MyComposition} durationInFrames={120} compositionWidth={1920} compositionHeight={1080} fps={30} inputProps={{videoUrl}} />}

      <input type="file" onChange={handleChange} />
    </div>
  );
};
```

The implementation of the `upload()` function is provider-specific and we do not show an implementation in this article. We assume it is a function that takes a file, uploads it and returns an URL.

## Optimistic updates

When we start the upload of the file, we can create a blob URL using `URL.createObjectURL()` which can be used to display the local file in a [`<Video>`](/docs/media/video) tag. When the file is done uploading and we get the remote URL, the component shall use remote URL as source.

```tsx twoslash title="App.tsx"
const MyComposition: React.FC<{videoUrl: string}> = ({videoUrl}) => {
  return null;
};

const upload = async (file: File) => {
  return 'https://example.com';
};

// ---cut---
import {Player} from '@remotion/player';
import {useCallback, useState} from 'react';

type VideoState =
  | {
      type: 'empty';
    }
  | {
      type: 'blob' | 'cloud';
      url: string;
    };

export const RemotionPlayer: React.FC = () => {
  const [videoState, setVideoState] = useState<VideoState>({
    type: 'empty',
  });

  const handleChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files === null) {
      return;
    }

    const file = event.target.files[0];
    const blobUrl = URL.createObjectURL(file);
    setVideoState({type: 'blob', url: blobUrl});
    const cloudUrl = await upload(file);
    setVideoState({type: 'cloud', url: cloudUrl});
    URL.revokeObjectURL(blobUrl);
  }, []);

  return (
    <div>
      {videoState.type !== 'empty' ? <Player component={MyComposition} durationInFrames={120} compositionWidth={1920} compositionHeight={1080} fps={30} inputProps={{videoUrl: videoState.url}} /> : null}
      <input type="file" onChange={handleChange} />
    </div>
  );
};
```

This will result in the user immediately seeing the video as they drag it into the input field. It is a good practice to call `URL.revokeObjectURL()` after the local video is not used anymore to free up the used memory.

:::note
As soon as possible, replace the `blob:` URL with the real URL.  
Since `blob:` URLs don't support the HTTP `Range` header, video seeking performance is degraded when using `blob:` URLs.
:::

## Validating video compatibility

Before uploading a video, you should validate whether the browser can decode it. This saves bandwidth and provides immediate feedback to the user. For a complete guide on validating user videos including handling incompatible formats, see [Validating user videos](/docs/validating-user-videos).

## See also

- [Uploading with presigned URLs](/docs/presigned-urls)
- [Validating user videos](/docs/validating-user-videos)
- [Video formats](/docs/miscellaneous/video-formats)
- [Check if video can be decoded](/docs/mediabunny/can-decode)
- [Mediabunny Documentation](https://mediabunny.dev)
